package com.example.voucher.service;

import java.beans.PropertyDescriptor;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.example.voucher.bo.VoucherItemAccessorHelper;
import com.example.voucher.config.bo.FieldReducingConfigBO;
import com.example.voucher.config.bo.MappingItemBO;
import com.example.voucher.config.bo.SubjectMappingConfigBO;
import com.example.voucher.config.bo.SubjectReducingConfigBO;
import com.example.voucher.config.util.EasMappingHandler;
import com.example.voucher.contract.VoucherItemAccessor;
import com.example.voucher.entity.Voucher;
import com.example.voucher.enums.ReducingRuleEnum;
import com.google.common.collect.Lists;

import ognl.Ognl;
import ognl.OgnlContext;

public abstract class AbstractVoucherItemConvertService implements VoucherItemConvertService {

    protected final Logger LOGGER = LoggerFactory.getLogger(getClass());

    @Autowired
    private EasMappingHandler easMappingHandler;

    protected final List<Voucher> convertWithConfig(List<VoucherItemAccessor> srcList, List<String> subjectList,
        Map<String, SubjectReducingConfigBO> reducingMap, Map<String, SubjectMappingConfigBO> mappingMap) {
        List<Voucher> descList = Lists.newArrayList();

        for (String subject : subjectList) {
            // 分组和汇总，得到后续映射需要的数据源
            List<VoucherItemAccessor> accessorList = reducing(srcList, reducingMap.get(subject));
            // 数据映射：VoucherItemAccessor ---> Voucher
            descList.addAll(mapping(accessorList, mappingMap.get(subject)));
        }
        return descList;
    }

    private List<VoucherItemAccessor> reducing(List<VoucherItemAccessor> list, SubjectReducingConfigBO subjectConfig) {
        List<VoucherItemAccessor> asstItemList = Lists.newArrayList();

        String entryCode = subjectConfig.getSubject();
        String asstItemKey = subjectConfig.getGroupingKeyOfAsstItem();
        String entryKey = subjectConfig.getGroupingKeyOfEntry();

        // 按照辅助账维度汇总
        List<String> reducingFieldList = VoucherItemAccessor.defaultAvailableReducingField();
        List<FieldReducingConfigBO> asstItemReducingList = reducingFieldList.stream()
            .map(t -> new FieldReducingConfigBO(t, t, asstItemKey, ReducingRuleEnum.SUMMARIZING.getCode(), null))
            .collect(Collectors.toList());
        Map<Object, VoucherItemAccessor> asstItemMap =
            VoucherItemAccessorHelper.groupingAndReducingVoucherItemAccessor(list, asstItemKey, asstItemReducingList);

        asstItemList.addAll(asstItemMap.values());

        // 按分录维度汇总
        List<Pair<String, String>> pairList = VoucherItemAccessor.defaultAvailableReducingPair();
        List<FieldReducingConfigBO> entryReducingList = pairList.stream().map(t -> new FieldReducingConfigBO(t.getLeft(), t.getRight(), entryKey,
            ReducingRuleEnum.SUMMARIZING.getCode(), null)).collect(Collectors.toList());
        Map<Object, VoucherItemAccessor> entryItemMap =
            VoucherItemAccessorHelper.groupingAndReducingVoucherItemAccessor(asstItemList, entryKey, entryReducingList);
        // 分录维度有些字段需要在辅助账的基础上汇总，赋值这些汇总字段（约定totalWeight汇总的是weight字段）
        for (VoucherItemAccessor accessor : asstItemList) {
            VoucherItemAccessor sumAsstItem =
                entryItemMap.get(VoucherItemAccessorHelper.getKeyInstanceFormVoucherItemAccessor(accessor, entryKey));
            if (sumAsstItem != null) {
                for (Pair<String, String> pair : pairList) {
                    try {
                        PropertyUtils.setProperty(accessor, pair.getRight(),
                            PropertyUtils.getProperty(sumAsstItem, pair.getLeft()));
                    } catch (Exception e) {
                        LOGGER.error("设置汇总字段{}发生异常", pair.getRight());
                        throw new RuntimeException(String.format("设置汇总字段{%s}发生异常", pair.getRight()), e);
                    }
                }
            }
        }

        // 自定义字段的汇总逻辑
        List<FieldReducingConfigBO> fieldReducingList = subjectConfig.getFieldReducingList();
        if (CollectionUtils.isNotEmpty(fieldReducingList)) {
            Map<String, List<FieldReducingConfigBO>> kMap =
                fieldReducingList.stream().collect(Collectors.groupingBy(FieldReducingConfigBO::getGroupingKey));
            // 按groupingKey分组
            for (Entry<String, List<FieldReducingConfigBO>> entry : kMap.entrySet()) {
                String specilKey = entry.getKey();
                List<FieldReducingConfigBO> configList = entry.getValue();
                Map<Object, VoucherItemAccessor> reducedAccessMap =
                    VoucherItemAccessorHelper.groupingAndReducingVoucherItemAccessor(list, specilKey, configList);
                for (VoucherItemAccessor accessor : asstItemList) {
                    VoucherItemAccessor reducedAccessor = reducedAccessMap
                        .get(VoucherItemAccessorHelper.getKeyInstanceFormVoucherItemAccessor(accessor, specilKey));
                    if (reducedAccessor != null) {
                        for (FieldReducingConfigBO config : fieldReducingList) {
                            try {
                                PropertyUtils.setProperty(accessor, config.getTargetField(),
                                    PropertyUtils.getProperty(reducedAccessor, config.getSrcField()));
                            } catch (Exception e) {
                                LOGGER.error("设置汇总字段{}发生异常", config.getTargetField());
                                throw new RuntimeException(String.format("设置汇总字段{%s}发生异常", config.getTargetField()), e);
                            }
                        }
                    }
                }
            }
        }

        return asstItemList;
    }

    /**
     * VoucherItemAccessor ---> Voucher
     * 
     * @param accessorList
     * @param mappingConfigList
     * @return
     */
    private List<Voucher> mapping(List<VoucherItemAccessor> accessorList, SubjectMappingConfigBO mappingConfig) {
        List<Voucher> destList = Lists.newArrayList();

        Set<String> targetFieldNameSet =
            Stream.of(Voucher.class.getDeclaredFields()).map(t -> t.getName()).collect(Collectors.toSet());
        for (VoucherItemAccessor accessor : accessorList) {
            Voucher po = new Voucher();

            OgnlContext context = new OgnlContext();
            context.put("easMappingHandler", easMappingHandler);
            context.setRoot(accessor);
            for (MappingItemBO mappingItem : mappingConfig.getList()) {
                String srcPropExpression = mappingItem.getSrcPropExpression();
                String targetPropName = mappingItem.getTargetPropName();
                if (!targetFieldNameSet.contains(targetPropName)) {
                    throw new RuntimeException(
                        String.format("配置有误，在类{%s}找不到属性{%s}", Voucher.class.getName(), targetPropName));
                }
                if (StringUtils.isNotBlank(srcPropExpression)) {
                    try {
                        Object expr = Ognl.parseExpression(srcPropExpression);
                        Object value = Ognl.getValue(expr, context, context.getRoot());
                        PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(po, targetPropName);
                        PropertyUtils.setProperty(po, targetPropName,
                            ConvertUtils.convert(value, pd.getPropertyType()));
                    } catch (Exception e) {
                        LOGGER.error("VoucherItemAccessor ---> Voucher发生异常", e);
                        throw new RuntimeException(e);
                    }
                }
            }

            destList.add(po);
        }
        return destList;
    }
}
